type ScrollableOpt = {
    /**
     * 容器元素
     */
    containerEl: HTMLElement | any
    /**
     * 列表元素(内容元素的父级元素，一遍是滚动的元素)
     */
    listEl?: HTMLElement
    /**
     * 滚动的元素(直接滚动的元素)
     */
    scrollEl: HTMLElement
    /**
     * 滚动的方向
     */
    direction?: "vertical" | "horizontal"
    /**
     * 滚动模式
     */
    mode?: "item" | "smooth"
    /**
     * 滚动间隔
     */
    interval?: number
    /**
     * 滚动时间
     */
    duration?: number
    /**
     * 块之间的空隙
     */
    itemGap?:
        | number
        | ((value: number, index: number, scrollable: Scrollable) => number)
    /**
     * 滚动速度（smooth模式下的）
     */
    speed?: number
    /**
     * 是否衔接滚动
     */
    continuous?: boolean

    /**
     * 初始的停止时间
     */
    startStop?: number

    /**
     * 滚动到底部的停止时间
     */
    endStop?: number
}

/**
 * 滚动类
 */
class Scrollable {
    /**
     * 容器元素
     */
    containerEl: any = null

    /**
     * 列表元素
     */
    listEl?: any = null

    // 滚动的元素
    scrollEl: any = null

    /**
     * 滚动方向
     */
    direction: "vertical" | "horizontal" = "vertical" // vertical horizontal

    /**
     * 滚动模式
     */
    mode: "item" | "smooth" = "item" // item smooth

    /**
     * 块之间的空隙
     */
    itemGap: ScrollableOpt["itemGap"]

    /**
     * 滚动间隔
     */
    interval = 5000

    /**
     * duration
     */
    duration = 700

    /**
     * 滚动的timer
     */
    scrollTimer: any /*NodeJS.Timeout = null*/

    /**
     * 总共个数
     */
    totalItemCount: any

    /**
     * 当前滚动的元素索引
     */
    scrollItemIndex = -1

    /**
     * smooth滚动模式下每次移动距离
     */
    speed = 1

    /**
     * 初始的停止时间
     */
    startStop = 3000

    /**
     * 滚动到底部的停止时间
     */
    endStop = 3000

    /**
     * 是否连续滚动
     */
    continuous: any = false
    /**
     * 克隆的索引
     */
    cloneIndex: any
    /**
     * 拷贝元素的值
     */
    cloneTotalValue: any
    /**
     * 是否正在运行
     */
    isRuning = false

    constructor(opt: ScrollableOpt) {
        this.initOpt(opt)
    }

    /**
     * 判断差异的key
     */
    get judgeKey() {
        return this.direction === "vertical" ? "height" : "width"
    }

    /**
     * 移动的key
     */
    get actionKey() {
        return this.direction === "vertical" ? "translateY" : "translateX"
    }

    /**
     * 使用的持续时间
     */
    get durationComputed() {
        return this.mode === "item" ? this.duration : 0
    }

    /**
     * 使用的间隔
     * item模式下，使用interval
     * smooth模式下，需要快速刷新，通常16ms，跟随人眼分辨率
     */
    get intervalComputed() {
        return this.mode === "item" ? this.interval : 16
    }

    /**
     * 初始化选项
     * @param opt
     */
    initOpt(opt: ScrollableOpt) {
        this.containerEl = opt.containerEl
        this.scrollEl = opt.scrollEl
        this.listEl = opt.listEl ?? opt.scrollEl
        this.direction = opt.direction ?? "vertical"
        this.mode = opt.mode ?? "item"

        this.interval = opt.interval ?? 5000
        this.duration = opt.duration ?? 700
        this.startStop = opt.startStop ?? 3000
        this.endStop = opt.endStop ?? 3000

        this.itemGap = opt.itemGap ?? 0

        this.speed = opt.speed ?? 1
        this.continuous = opt.continuous
    }

    /**
     * 初始化滚动
     * @returns
     */
    async init() {
        const {containerEl, scrollEl, judgeKey} = this

        if (!containerEl || !scrollEl) {
            console.log("元素不存在，请确认元素已挂载")
            return false
        }

        // 判断是否需要滚动
        if (
            scrollEl.getBoundingClientRect()[judgeKey] <=
            containerEl.getBoundingClientRect()[judgeKey]
        ) {
            console.log("不需要滚动")
            return false
        }

        this.totalItemCount = this.listEl.children.length
        this.scrollItemIndex = 0
        scrollEl.setAttribute("data-scroll", "0")

        // 如果有连续的滚动需求
        // 分析当前直接显示的子元素的个数
        // 拷贝直接显示的子元素，插入到底部
        if (this.continuous) {
            this.analyzeCloneNode()
            await this.sleep(100)
            this.start()
        } else {
            this.start()
        }
    }

    analyzeCloneNode() {
        const {containerEl, listEl, judgeKey} = this

        const children = listEl.children
        const result = containerEl.getBoundingClientRect()[judgeKey]
        let tempValue = 0
        for (let index = 0; index < children.length; index++) {
            const childEl = children[index]
            const value = childEl.getBoundingClientRect()[judgeKey]
            tempValue += value
            if (tempValue > result) {
                // 超出显示视图了
                this.cloneIndex = index - 1
                this.cloneTotalValue = tempValue - value
                break
            } else {
                this.listEl.appendChild(childEl.cloneNode(true))
            }
        }
    }

    /**
     * 重置
     */
    reset() {
        const {scrollEl, actionKey} = this
        this.scrollItemIndex = 0
        scrollEl.setAttribute("data-scroll", "0")
        scrollEl.style.transform = `${actionKey}(0px)`
    }

    /**
     * 开始滚动
     */
    start() {
        if (this.scrollTimer) {
            clearTimeout(this.scrollTimer)
        }
        this.isRuning = true
        this.scroll()
    }

    /**
     * 中止
     */
    purse() {
        if (this.scrollTimer) {
            clearTimeout(this.scrollTimer)
            this.scrollTimer = null
        }
        this.isRuning = false
    }

    /**
     * 重新开始
     */
    restart() {
        if (this.scrollTimer) {
            clearTimeout(this.scrollTimer)
        }
        this.isRuning = true
        this.scroll()
    }

    /**
     * 休眠
     * @param time 休眠时间ms
     * @returns
     */
    async sleep(time: number) {
        return new Promise((resolve) => setTimeout(resolve, time))
    }

    /**
     * 更新动画
     */
    updateTransition(durationComputed: number) {
        this.scrollEl.style.transition = `all ${durationComputed}ms`
    }

    /**
     * 滚动
     */
    async scroll() {
        if (!this.isRuning) {
            return
        }

        const {
            containerEl,
            listEl,
            scrollEl,
            judgeKey,
            actionKey,
            durationComputed,
            scrollItemIndex,
            intervalComputed,
            itemGap,
            mode,
            speed,
            endStop,
            startStop
        } = this

        if (scrollItemIndex === 0) {
            if (!this.continuous) {
                // 在第一个位置
                // 休眠初始停止时间
                await this.sleep(startStop)
            }
        }

        // 本次的滚动距离
        let scrollData = 0

        if (mode === "item") {
            // 块滚动
            // 设置滚动动画样式
            this.updateTransition(durationComputed)
            // 提取下一个滚动的元苏
            const scrollItem = listEl.children[scrollItemIndex]

            // 当前的位置
            const currentScrollValue = scrollItem.getBoundingClientRect()[judgeKey]

            // 计算间隙
            const gapValue =
                typeof itemGap === "function"
                    ? itemGap(currentScrollValue, scrollItemIndex, this)
                    : itemGap

            // 设置最终的滚动距离
            scrollData = currentScrollValue + gapValue
        } else if (mode === "smooth") {
            scrollData = speed
        }

        // 提取上次的滚动位置
        const originData = Number(scrollEl.getAttribute("data-scroll"))

        // 设置新的滚动位置
        const newScrollData = originData + scrollData
        scrollEl.setAttribute("data-scroll", newScrollData.toString())

        // 更新样式
        scrollEl.style.transform = `${actionKey}(${-newScrollData}px)`

        // 设置下一次滚动
        this.scrollTimer = setTimeout(async () => {
            if (this.continuous) {
                if (this.scrollItemIndex === this.totalItemCount - 1) {
                    this.updateTransition(0)
                    this.reset()
                    // 重新滚动
                    setTimeout(() => {
                        this.scroll()
                    }, 10)
                } else {
                    this.scrollItemIndex += 1
                    this.scroll()
                }
            } else {
                // 非连续的
                const containerData = containerEl.getBoundingClientRect()[judgeKey]
                const listData = listEl.getBoundingClientRect()[judgeKey]
                // 判断最新的滚动位置是否已经到底
                if (newScrollData >= listData - containerData) {
                    // 已经是最后一个
                    // 停留最后一个
                    await this.sleep(endStop)
                    // 回到初始
                    this.reset()
                    // 重新滚动
                    this.scrollTimer = setTimeout(() => {
                        this.scroll()
                    }, intervalComputed)
                } else {
                    // 不是最后一个
                    this.scrollItemIndex += 1
                    this.scroll()
                }
            }

        }, intervalComputed)
    }

    /**
     * 释放
     */
    dispose() {
        this.scrollEl.style.transition = null
        this.containerEl = this.listEl = this.scrollEl = null
        this.scrollTimer && clearTimeout(this.scrollTimer)
    }
}

export default Scrollable
